package com.jmebananas.game.tile;

import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.image.BufferedImage;
import java.nio.FloatBuffer;

import com.jme.image.Texture;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.TriMesh;
import com.jme.scene.batch.TriangleBatch;
import com.jme.scene.state.TextureState;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;
import com.jme.util.geom.BufferUtils;
import com.jmebananas.bounding.MinimumBoundingBox;
import com.jmebananas.image.util.ImageUtil;

/**
 * Tile
 * 
 * @author Glenn Powell
 * 
 */
public class Tile extends Node {

	private static final long serialVersionUID = 1L;
	
	private static final Rectangle _tempTileBound = new Rectangle();


	protected TriMesh quad = null;

	protected TextureState textureState = null;
	
	protected AffineTransform worldTileTransform = new AffineTransform();
	
	protected Shape worldTileBound = null;
	
	protected boolean pickAlphaMask = true;


	public Tile(String name) {
		this(name, (Texture) null);
	}

	public Tile(String name, BufferedImage image) {
		super(name);

		initialize(name);

		setImage(image, true);
	}

	public Tile(String name, Texture texture) {
		super(name);

		initialize(name);

		setTexture(texture, true);
	}

	private void initialize(String name) {
		quad = new TriMesh(name + "_mesh");
		
		TriangleBatch batch = quad.getBatch(0);
		batch.setVertexCount(4);
		batch.setVertexBuffer(BufferUtils.createVector3Buffer(batch.getVertexCount()));
		batch.setNormalBuffer(BufferUtils.createVector3Buffer(batch.getVertexCount()));
		FloatBuffer textureBuffer = BufferUtils.createVector2Buffer(batch.getVertexCount());
		quad.setTextureBuffer(0, textureBuffer);
		batch.setTriangleQuantity(2);
		batch.setIndexBuffer(BufferUtils.createIntBuffer(batch.getTriangleCount() * 3));

		batch.getVertexBuffer().put(0).put(1).put(0);
		batch.getVertexBuffer().put(0).put(0).put(0);
		batch.getVertexBuffer().put(1).put(0).put(0);
		batch.getVertexBuffer().put(1).put(1).put(0);

		batch.getNormalBuffer().put(0).put(0).put(1);
		batch.getNormalBuffer().put(0).put(0).put(1);
		batch.getNormalBuffer().put(0).put(0).put(1);
		batch.getNormalBuffer().put(0).put(0).put(1);

		textureBuffer.put(0).put(1);
		textureBuffer.put(0).put(0);
		textureBuffer.put(1).put(0);
		textureBuffer.put(1).put(1);

		batch.getIndexBuffer().put(0);
		batch.getIndexBuffer().put(1);
		batch.getIndexBuffer().put(2);
		batch.getIndexBuffer().put(0);
		batch.getIndexBuffer().put(2);
		batch.getIndexBuffer().put(3);

		textureState = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
		textureState.setEnabled(true);
		quad.setRenderState(textureState);
		
		MinimumBoundingBox bound = new MinimumBoundingBox();
		bound.getMinimum().z = 0.1f;
		quad.setModelBound(bound);
		quad.updateModelBound();

		attachChild(quad);
	}
	
	public TriMesh getMesh() {
		return quad;
	}

	public void setImage(BufferedImage image) {
		setImage(image, true);
	}

	public void setImage(BufferedImage image, boolean resize) {
		if (image != null) {
			Texture texture = TextureManager.loadTexture(image, Texture.MM_LINEAR, Texture.FM_LINEAR, 1.0f, com.jme.image.Image.GUESS_FORMAT_NO_S3TC, false);
			texture.setWrap(Texture.WM_WRAP_S_WRAP_T);
			textureState.setTexture(texture);

			if (resize)
				setSize(image.getWidth(), image.getHeight());
		} else {
			textureState.clearTextures();
		}
	}

	public void setTexture(Texture texture) {
		setTexture(texture, true);
	}

	public void setTexture(Texture texture, boolean resize) {
		if (texture != null) {
			textureState.setTexture(texture);

			if (resize)
				setSize(texture.getImage().getWidth(), texture.getImage().getHeight());
		} else {
			textureState.clearTextures();
		}
	}
	
	public TextureState getTextureState() {
		return textureState;
	}
	
	public boolean isPickAlphaMask() {
		return pickAlphaMask;
	}
	
	public boolean hasPickAlphaMask() {
		return pickAlphaMask && (textureState.getTexture() != null);
	}

	public void setPickAlphaMask(boolean pickAlphaMask) {
		this.pickAlphaMask = pickAlphaMask;
	}

	public float getX() {
		return quad.getWorldTranslation().x;
	}

	public float getY() {
		return quad.getWorldTranslation().y;
	}

	public void setSize(float width, float height) {
		quad.getLocalScale().x = width;
		quad.getLocalScale().y = height;
	}

	public float getWidth() {
		return quad.getLocalScale().x;
	}

	public float getHeight() {
		return quad.getLocalScale().y;
	}

	public float getAngle() {
		Vector3f axis = new Vector3f();
		float angle = quad.getWorldRotation().toAngleAxis(axis);
		return angle * axis.z;
	}

	public void setTiling(float tileX, float tileY) {
		setTextureCoords(0, 0, tileX, tileY);
	}
	
	public void updateWorldData(float tpf) {
		super.updateWorldData(tpf);
		
		worldTileTransform.setToTranslation(getX(), getY());
		worldTileTransform.rotate(getAngle());
		worldTileTransform.scale(getWorldScale().x, getWorldScale().y);
		_tempTileBound.setBounds(0, 0, (int) getWidth(), (int) getHeight());
		worldTileBound = worldTileTransform.createTransformedShape(_tempTileBound);
	}

	public AffineTransform getWorldTileTransform() {
		return worldTileTransform;
	}

	public Shape getWorldTileBound() {
		return worldTileBound;
	}

	public boolean pickTile(Point point, Point localPoint) {
		return pickTile(point, localPoint, pickAlphaMask);
	}
	
	public boolean pickTile(Point point, Point localPoint, boolean pickAlphaMask) {
		Point worldPoint = point;
		if (worldPoint == localPoint)
			worldPoint = new Point(worldPoint);
		localPoint.setLocation(worldPoint);
		try {
			worldTileTransform.inverseTransform(localPoint, localPoint);
		} catch (NoninvertibleTransformException e) {
		}

		if (worldTileBound.contains(worldPoint)) {
			if (pickAlphaMask && hasPickAlphaMask()) {
				Rectangle subBound = getSubimageBound();
				int imgX = localPoint.x + subBound.x;
				int imgY = localPoint.y + subBound.y;
				float alpha = ImageUtil.getAlpha(getTextureState().getTexture().getImage(), imgX, imgY);
				if (alpha < FastMath.FLT_EPSILON)
					return false;
			}
			return true;
		}
		return false;
	}

	public void setSubimageBound(Rectangle bounds) {
		setSubimageBound(bounds.x, bounds.y, bounds.width, bounds.height);
	}

	public void setSubimageBound(int x, int y, int width, int height) {
		if (textureState.getTexture() != null) {
			float imageWidth = textureState.getTexture().getImage().getWidth();
			float imageHeight = textureState.getTexture().getImage().getHeight();
			float x1 = x / imageWidth;
			float y1 = y / imageHeight;
			float x2 = (x + width) / imageWidth;
			float y2 = (y + height) / imageHeight;
			setTextureCoords(x1, y1, x2, y2);
		}
	}

	private void setTextureCoords(float x1, float y1, float x2, float y2) {
		FloatBuffer textureBuffer = quad.getTextureBuffer(0, 0);
		textureBuffer.clear();
		textureBuffer.put(x1).put(y2);
		textureBuffer.put(x1).put(y1);
		textureBuffer.put(x2).put(y1);
		textureBuffer.put(x2).put(y2);
	}

	public Rectangle getSubimageBound() {
		if (textureState.getTexture() != null) {
			int imageWidth = textureState.getTexture().getImage().getWidth();
			int imageHeight = textureState.getTexture().getImage().getHeight();
			FloatBuffer textureBuffer = quad.getTextureBuffer(0, 0);
			int x = (int) (textureBuffer.get(0) * imageWidth);
			int y = (int) (textureBuffer.get(3) * imageHeight);
			int width = (int) (textureBuffer.get(4) * imageWidth) - x;
			int height = (int) (textureBuffer.get(1) * imageHeight) - y;
			return new Rectangle(x, y, width, height);
		}
		return new Rectangle(0, 0, (int) getWidth(), (int) getHeight());
	}

	public float getAnchorX() {
		return -quad.getLocalTranslation().x;
	}

	public float getAnchorY() {
		return -quad.getLocalTranslation().y;
	}

	public void setAnchor(float x, float y) {
		quad.setLocalTranslation(-x, -y, 0);
	}

	public ColorRGBA getColor() {
		return quad.getBatch(0).getDefaultColor();
	}

	public void setColor(ColorRGBA color) {
		quad.getBatch(0).getDefaultColor().set(color);
	}

	public void setColor(float r, float g, float b, float a) {
		quad.getBatch(0).getDefaultColor().set(r, g, b, a);
	}

	public void setAlpha(float alpha) {
		quad.getBatch(0).getDefaultColor().a = alpha;
	}

	public void onButton(int button, boolean pressed, int x, int y) {
	}
	
	public void onEnter(int x, int y) {
	}
	
	public void onExit() {
	}

	public void onMove(int x, int y) {
	}
	
	public void onWheel(int wheelDelta, int x, int y) {
	}

}
